/**
 * 
 */
package com.roshan.web.security.provider;

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.StringUtils;

import com.roshan.domain.MfaCredential;
import com.roshan.domain.MfaQuestion;
import com.roshan.domain.User;
import com.roshan.service.UserService;
import com.roshan.util.constants.ExceptionMessageConstants;
import com.roshan.web.dto.SecurityQuestionProfile;
import com.roshan.web.security.domain.RoarAuthenticationToken;
import com.roshan.web.security.exceptions.RoarInsufficientAuthenticationException;
import com.roshan.web.security.exceptions.UserDeactivatedException;
import com.roshan.web.service.ISecurityManagerService;
import com.roshan.web.util.constants.LoggerWebMessages;

public class RoarAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private UserService userService;

    @Value("${roar.mfa.enabled}")
    private boolean mfaEnabled;

    private final Logger logger = Logger.getLogger(getClass());

    @Autowired
    private ISecurityManagerService securityManagerService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        RoarAuthenticationToken authenticationToken = (RoarAuthenticationToken) authentication;

        String username = (authenticationToken.getPrincipal() != null) ? authenticationToken.getPrincipal().toString()
                : null;
        String password = (authenticationToken.getCredentials() != null) ? authenticationToken.getCredentials()
                .toString() : null;
        String securityQuestion = authenticationToken.getSecurityQuestion();

        boolean checkPassword = authenticationToken.isCheckPassword();
        boolean checkMFA = this.mfaEnabled && authenticationToken.isCheckMFA();

        String userUri = null;

        if (checkPassword) {

            User user = validatePassword(username, password);

            userUri = user.getUri().toString();
            authenticationToken.setUserUri(userUri);

            if (checkMFA) {
                checkMFA = this.mfaEnabled && (user.getMfaCredentials() != null && !user.getMfaCredentials().isEmpty());
            }

            if (checkMFA) {

                MfaCredential mfaCredential = user.getMfaCredentials().get(0);
                MfaQuestion mfaQuestion = mfaCredential.getMfaQuestion();

                if (mfaQuestion != null) {
                    securityQuestion = mfaQuestion.getQuestion();
                    authenticationToken.setSecurityQuestion(securityQuestion);
                }
            }
        }

        if (checkMFA) {
            String customerUri = StringUtils.hasText(userUri) ? userUri : authenticationToken.getUserUri();
            if (this.mfaEnabled && StringUtils.hasText(securityQuestion)) {
                validateSecurityQuestionAndAnswer(username, securityQuestion, authenticationToken.getSecurityAnswer(),
                        customerUri);
            }
        }

        if (!checkPassword && !checkMFA) {
            return authenticationToken;
        }

        userUri = authenticationToken.getUserUri();

        RoarAuthenticationToken token = retrieveUser(username, password, userUri, securityQuestion, authenticationToken
                .getSecurityAnswer(), checkPassword, checkMFA);

        return token;

    }

    private RoarAuthenticationToken retrieveUser(String username, String password, String userUri,
            String securityQuestion, String securityAnswer, boolean checkPassword, boolean checkMFA) {

        String userURI = userUri;

        if (userURI == null) {

            User user = this.userService.retrieveUserByUsername(username, false);
            if (user == null) {
                throw new UsernameNotFoundException(ExceptionMessageConstants.USERNAME_NOT_FOUND_WHILE_LOGGING_IN);
            }

            userURI = user.getUri().toString();
        }

        User user = this.userService.retrieveUser(userURI);
        if (user == null) {
            throw new UsernameNotFoundException(ExceptionMessageConstants.USERNAME_NOT_FOUND_WHILE_LOGGING_IN);
        }

        // TODO fix this

        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
        authorities.add(new GrantedAuthorityImpl("ROLE_USER"));

        RoarAuthenticationToken token = new RoarAuthenticationToken(username, password, authorities, user
                .getFirstName(), user.getLastName(), userUri, securityQuestion, securityAnswer, checkPassword, checkMFA);

        return token;
    }

    private User validatePassword(String username, String presentedPassword) {

        User user = this.userService.validatePassword(username, presentedPassword, true);

        if (user == null) {
            String message = LoggerWebMessages.ERROR_OCCURED_WHILE_LOGGING_IN_USER_CANNOT_LOGIN_USER_AT_THIS_TIME;
            this.logger.info(message);
            throw new BadCredentialsException(message);
        }

        if (!user.isActive()) {
            String message = LoggerWebMessages.THE_USER_IS_DEACTIVATED_DUE_TO_LACK_OF_LOGIN_ATTEMPTS;
            this.logger.error(message);
            throw new UserDeactivatedException(message);
        }

        return user;

    }

    private void validateSecurityQuestionAndAnswer(String userName, String securityQuestion, String securityAnswer,
            String customerUri) {

        String message = LoggerWebMessages.ERROR_OCCURED_WHILE_LOGGING_IN_USER_CANNOT_LOGIN_USER_AT_THIS_TIME;

        if (!StringUtils.hasText(securityAnswer)) {
            throw new RoarInsufficientAuthenticationException(
                    LoggerWebMessages.SECURITY_QUESTION_AND_ANSWER_IS_REQUIRED, userName, securityQuestion, customerUri);

        } else if ((StringUtils.hasText(securityQuestion) && !StringUtils.hasText(securityAnswer))
                || (!StringUtils.hasText(securityQuestion) && StringUtils.hasText(securityAnswer))) {

            this.logger.info(message);
            throw new BadCredentialsException(message);
        }

        SecurityQuestionProfile securityQuestionProfile = new SecurityQuestionProfile();

        securityQuestionProfile.setCustomerUri(customerUri);
        securityQuestionProfile.setMfaAnswer(securityAnswer);
        securityQuestionProfile.setMfaQuestion(securityQuestion);

        boolean validated = this.securityManagerService.validateSecurityAnswer(securityQuestionProfile);

        if (!validated) {
            this.logger.info(message);
            throw new BadCredentialsException(message);
        }

    }

    @Override
    public boolean supports(Class<? extends Object> authentication) {
        return RoarAuthenticationToken.class.isAssignableFrom(authentication);
    }

}
